//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

public import Foundation
public import LanguageServerProtocol

/// A `build/taskFinish` notification must always be sent after a `build/taskStart`` with the same `taskId` was sent.
public struct TaskFinishNotification: NotificationType {
  public static let method: String = "build/taskFinish"

  /// Unique id of the task with optional reference to parent task id.
  public var taskId: TaskId

  /// A unique identifier generated by the client to identify this request.
  public var originId: String?

  /// Timestamp of when the event started in milliseconds since Epoch.
  @CustomCodable<MillisecondsSince1970Date?>
  public var eventTime: Date?

  /// Message describing the task.
  public var message: String?

  /// Task completion status.
  public var status: StatusCode

  /// Kind of data to expect in the `data` field. If this field is not set, the kind of data is not specified.
  public var dataKind: TaskFinishDataKind?

  /// Optional metadata about the task.
  ///
  /// Objects for specific tasks like compile, test, etc are specified in the protocol.
  public var data: LSPAny?

  public init(
    taskId: TaskId,
    originId: String? = nil,
    eventTime: Date? = nil,
    message: String? = nil,
    status: StatusCode,
    dataKind: TaskFinishDataKind? = nil,
    data: LSPAny? = nil
  ) {
    self.taskId = taskId
    self.originId = originId
    self.eventTime = eventTime
    self.message = message
    self.status = status
    self.dataKind = dataKind
    self.data = data
  }

}

/// Task finish notifications may contain an arbitrary interface in their `data` field.
///
/// The kind of interface that is contained in a notification must be specified in the `dataKind` field.
public struct TaskFinishDataKind: RawRepresentable, Codable, Hashable, Sendable {
  public let rawValue: String
  public init(rawValue: String) {
    self.rawValue = rawValue
  }

  /// `data` field must contain a `CompileReport` object.
  public static let compileReport = TaskStartDataKind(rawValue: "compile-report")

  /// `data` field must contain a `TestFinish` object.
  public static let testFinish = TaskStartDataKind(rawValue: "test-finish")

  /// `data` field must contain a `TestReport` object.
  public static let testReport = TaskStartDataKind(rawValue: "test-report")
}

/// This structure is embedded in the `TaskFinishNotification.data` field, when the `dataKind` field contains
/// `"compile-report"`.
///
/// The completion of a compilation task should be signalled with a `build/taskFinish` notification. When the
/// compilation unit is a build target, the notification's `dataKind` field must be `compile-report` and the `data`
/// field must include a `CompileReportData` object.
public struct CompileReportData: Codable, Hashable, Sendable {
  /// The build target that was compiled.
  public var target: BuildTargetIdentifier

  /// An optional request id to know the origin of this report.
  ///
  /// Deprecated: Use the field in TaskFinishParams instead .
  public var originId: String?

  /// The total number of reported errors compiling this target.
  public var errors: Int

  /// The total number of reported warnings compiling the target.
  public var warnings: Int

  /// The total number of milliseconds it took to compile the target.
  @CustomCodable<MillisecondsSince1970Date?>
  public var time: Date?

  /// The compilation was a noOp compilation.
  public var noOp: Bool?

  public init(
    target: BuildTargetIdentifier,
    originId: String? = nil,
    errors: Int,
    warnings: Int,
    time: Date? = nil,
    noOp: Bool? = nil
  ) {
    self.target = target
    self.originId = originId
    self.errors = errors
    self.warnings = warnings
    self.time = time
    self.noOp = noOp
  }
}

/// This structure is embedded in the `TaskFinishNotification.data` field, when the `dataKind` field contains
/// `"test-finish"`.
public struct TestFinishData: Codable, Hashable, Sendable {
  /// Name or description of the test.
  public var displayName: String?

  /// Information about completion of the test, for example an error message.
  public var message: String?

  /// Completion status of the test.
  public var status: TestStatus

  /// Source location of the test, as LSP location.
  public var location: Location?

  /// Kind of data to expect in the `data` field. If this field is not set, the kind of data is not specified.
  public var dataKind: TestFinishDataKind?

  /// Optionally, structured metadata about the test completion.
  /// For example: stack traces, expected/actual values.
  public var data: LSPAny?

  public init(
    displayName: String? = nil,
    message: String? = nil,
    status: TestStatus,
    location: Location? = nil,
    dataKind: TestFinishDataKind? = nil,
    data: LSPAny? = nil
  ) {
    self.displayName = displayName
    self.message = message
    self.status = status
    self.location = location
    self.dataKind = dataKind
    self.data = data
  }

}

public enum TestStatus: Int, Codable, Hashable, Sendable {
  /// The test passed successfully.
  case passed = 1

  /// The test failed.
  case failed = 2

  /// The test was marked as ignored.
  case ignored = 3

  /// The test execution was cancelled.
  case cancelled = 4

  /// The was not included in execution.
  case skipped = 5
}

public struct TestFinishDataKind: RawRepresentable, Codable, Hashable, Sendable {
  public var rawValue: String

  public init?(rawValue: String) {
    self.rawValue = rawValue
  }
}

/// This structure is embedded in the `TaskFinishNotification.data` field, when the `dataKind` field contains
/// `"test-report"`.
public struct TestReportData: Codable, Hashable, Sendable {
  /// Deprecated: Use the field in TaskFinishParams instead
  public var originId: String?

  /// The build target that was compiled.
  public var target: BuildTargetIdentifier

  /// The total number of successful tests.
  public var passed: Int

  /// The total number of failed tests.
  public var failed: Int

  /// The total number of ignored tests.
  public var ignored: Int

  /// The total number of cancelled tests.
  public var cancelled: Int

  /// The total number of skipped tests.
  public var skipped: Int

  /// The total number of milliseconds tests take to run (e.g. doesn't include compile times).
  public var time: Int64?

  public init(
    originId: String? = nil,
    target: BuildTargetIdentifier,
    passed: Int,
    failed: Int,
    ignored: Int,
    cancelled: Int,
    skipped: Int,
    time: Int64? = nil
  ) {
    self.originId = originId
    self.target = target
    self.passed = passed
    self.failed = failed
    self.ignored = ignored
    self.cancelled = cancelled
    self.skipped = skipped
    self.time = time
  }

}
